package com.dc.aimc.auth.service.impl;

import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dc.aimc.auth.entity.Permission;
import com.dc.aimc.auth.entity.Role;
import com.dc.aimc.auth.entity.RolePermission;
import com.dc.aimc.auth.entity.UserRole;
import com.dc.aimc.auth.service.*;
import com.dc.aimc.auth.vo.LoginResult;
import com.dc.aimc.common.core.entity.BaseEntity;
import com.dc.aimc.common.redis.util.RedisUtil;
import com.dc.aimc.common.service.exception.CustomException;
import com.dc.aimc.common.service.response.JsonResponse;
import com.dc.aimc.common.service.util.JwtUtil;
import com.dc.aimc.user.api.UserFeignClient;
import com.dc.aimc.user.entity.App;
import com.dc.aimc.user.entity.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.sql.Date;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RefreshScope
public class AuthServiceImpl implements AuthService {

    @Autowired
    private RedisUtil<String, String> redisUtil;
    @Autowired
    private UserFeignClient userService;
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private RoleService roleService;

    @Autowired
    private RolePermissionService rolePermissionService;
    @Autowired
    private PermissionService permissionService;

    @Autowired
    private AuthenticationManager authenticationManager;
    @Value("${jwt.expires}")
    private Long expires;


    @Override
    public User getUserInfo(String username) {
        JsonResponse<User> response = userService.getUserByUsername(username);
        if (response != null) {
            return response.getData();
        }
        return null;
    }

    @Override
    public List<SimpleGrantedAuthority> getAuthorities(Integer userId) {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        // 角色集合
        LambdaQueryWrapper<UserRole> userRoleWrapper = new LambdaQueryWrapper<>();
        userRoleWrapper.select(UserRole::getRoleId).eq(UserRole::getUserId, userId);
        List<Integer> roleIds = userRoleService.list(userRoleWrapper).stream().map(UserRole::getRoleId).filter(Objects::nonNull).collect(Collectors.toList());
        if (roleIds.size() > 0) {
            LambdaQueryWrapper<Role> roleWrapper = new LambdaQueryWrapper<>();
            roleWrapper.select(Role::getRole).in(BaseEntity::getId, roleIds);
            roleService.list(roleWrapper).stream().filter(r -> StringUtils.isNotBlank(r.getRole())).forEach(r -> authorities.add(new SimpleGrantedAuthority("ROLE_" + r.getRole())));

            // 权限集合
            LambdaQueryWrapper<RolePermission> rolePermissionWrapper = new LambdaQueryWrapper<>();
            rolePermissionWrapper.select(RolePermission::getPermissionId).in(RolePermission::getRoleId, roleIds);
            List<Integer> permissionIds = rolePermissionService.list(rolePermissionWrapper).stream().map(RolePermission::getPermissionId).filter(Objects::nonNull).collect(Collectors.toList());
            if (permissionIds.size() > 0) {
                LambdaQueryWrapper<Permission> permissionWrapper = new LambdaQueryWrapper<>();
                permissionWrapper.select(Permission::getPermission).in(BaseEntity::getId, permissionIds);
                permissionService.list(permissionWrapper).stream().filter(p -> StringUtils.isNotBlank(p.getPermission())).forEach(p -> authorities.add(new SimpleGrantedAuthority(p.getPermission())));
            }
        }
        return authorities;
    }

    @Override
    public LoginResult login(String username, String password) {
        // 认证
        Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        // 保存认证结果
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 生成token
        Map<String, Object> info = new HashMap<>();
        info.put("jti", UUID.randomUUID().toString());
        info.put("username", authentication.getName());
        List<String> authorities = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
        info.put("authorities", authorities);
        java.util.Date expireDate = new java.util.Date(System.currentTimeMillis() + expires);
        info.put("exp", expireDate.getTime() / 1000);
        String token = JwtUtil.signToken(info, expireDate);
        // 缓存token
        redisUtil.hset("user_token_cache", info.get("jti").toString(), token);
        return new LoginResult(token, "bearer", (expireDate.getTime() - System.currentTimeMillis()) / 1000);
    }

    @Override
    public LoginResult appLogin(String appId, String appSignature, Long timestamp) {
        // 密码登录校验
        JsonResponse<App> response = userService.getAppByAppId(appId);
        App app = response.getData();
        if (app == null) {
            throw new CustomException("查询不到此应用信息", 400);
        }
        /*
        签名校验规则：
        1. 将appId进行SHA1加密，判断是否与appSecret一致
        2. 校验签名时效性：允许前后误差不超过10秒
        3. 将appId、timestamp、appSecret组合后进行SHA1加密，判断是否与签名(signature)一致
         */
        if (!app.getAppSecret().equals(SecureUtil.sha1(appId))) {
            throw new CustomException("签名校验失败", 400);
        }
        long difference = System.currentTimeMillis() - timestamp;
        if (difference > 10000L || difference < -10000L) {
            throw new CustomException("签名过期", 400);
        }
        // SHA1(appId + timestamp + secret)
        String regulation = "appId=" + appId + "&timestamp=" + timestamp + "&appSecret=" + app.getAppSecret();
        boolean equals = appSignature.equals(SecureUtil.sha1(regulation));
        if (!equals) {
            throw new CustomException("签名校验失败", 400);
        }
        // token附带信息
        Map<String, String> info = new HashMap<>();
        info.put("appId", app.getAppId());
        // 签发token
        String token = JwtUtil.signToken(info, Date.from(LocalDate.now().plusDays(7).atStartOfDay(ZoneOffset.ofHours(8)).toInstant()));
        // 缓存token
        redisUtil.hset("app_token_cache", app.getId().toString(), token);

        return new LoginResult(token, app);
    }

}
